﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Linq;
using System.Xml.Linq;
using System.Text;

namespace Gmantis.Uploader
{
    public class UploadManagerWebService : IUploadManager
    {
        // Fields
        private bool _hasAborted;
        private UploadFilePartPackage _package;
        private Dictionary<string, string> _parameters;
        private HttpWebRequest _request;
        private UploadManagerSettingsWebService _settings;

        // Events
        public event EventHandler<UploadCompletedEventArgs> UploadCompleted;
        public event EventHandler<UploadProgressChangedEventArgs> UploadProgressChanged;

        // Methods
        public bool BeginUpload(UploadManagerSettings settings, UploadFilePartPackage package, Dictionary<HttpRequestHeader, string> headers, Dictionary<string, string> parameters)
        {
            this._settings = settings as UploadManagerSettingsWebService;
            this._package = package;
            this._parameters = parameters;
            try
            {
                this._request = (HttpWebRequest)WebRequest.Create(this._settings.Address);
                this._hasAborted = false;
                foreach (KeyValuePair<HttpRequestHeader, string> pair in headers)
                {
                    this._request.Headers[pair.Key] = pair.Value;
                }
            }
            catch (NotSupportedException exception)
            {
                throw new InvalidAddressException(exception);
            }
            this._request.Method = "POST";
            this._request.ContentType = "text/xml; charset=utf-8";
            this._request.Headers["SOAPAction"] = this._settings.Namespace + (this._package.ServerWaitForOneFile ? this._settings.MethodOneFile : this._settings.MethodManyFiles);
            this._request.BeginGetRequestStream(new AsyncCallback(this.RequestProceed), null);
            return true;
        }

        public void Cancel()
        {
            if (this._request != null)
            {
                lock (this._request)
                {
                    this._hasAborted = true;
                    this._request.Abort();
                }
            }
        }

        private byte[] ConvertToByteArray(string s)
        {
            return Encoding.UTF8.GetBytes(s);
        }

        private void OnUploadCompleted(bool success, bool cancelled, Exception exc, string response)
        {
            if (this.UploadCompleted != null)
            {
                this.UploadCompleted(this, new UploadCompletedEventArgs(success, cancelled, exc, null, response, null));
            }
        }

        private void RequestProceed(IAsyncResult asyncResult)
        {
            if (!this._hasAborted)
            {
                BinaryWriter writer;
                try
                {
                    writer = new BinaryWriter(this._request.EndGetRequestStream(asyncResult), Encoding.UTF8);
                }
                catch (Exception exception)
                {
                    this.OnUploadCompleted(false, false, new Exception("There was an error trying to get the request stream", exception), string.Empty);
                    return;
                }
                try
                {
                    if (this._package.ServerWaitForOneFile)
                    {
                        writer.Write(this.ConvertToByteArray("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n                                                            <soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n                                                              <soap:Body>\r\n                                                                <" + this._settings.MethodOneFile + " xmlns=\"" + this._settings.Namespace + "\">\r\n                                                                  <" + this._settings.ParamFileName + ">" + this._package[0].Name + "</" + this._settings.ParamFileName + ">\r\n                                                                  <" + this._settings.ParamFileData + ">" + Convert.ToBase64String(this._package[0].Data) + "</" + this._settings.ParamFileData + ">\r\n                                                                  <" + this._settings.ParamParameters + ">"));
                        foreach (KeyValuePair<string, string> pair in this._parameters)
                        {
                            writer.Write(this.ConvertToByteArray("<Parameter><Key><![CDATA[" + pair.Key + "]]></Key><Value><![CDATA[" + pair.Value + "]]></Value></Parameter>"));
                        }
                        writer.Write(this.ConvertToByteArray(string.Concat(new object[] { 
                        "</", this._settings.ParamParameters, ">\r\n                                                                  <", this._settings.ParamPartCount, ">", this._package[0].PartCount, "</", this._settings.ParamPartCount, ">\r\n                                                                  <", this._settings.ParamPartNumber, ">", this._package[0].PartNumber, "</", this._settings.ParamPartNumber, ">\r\n                                                                </", this._settings.MethodOneFile, 
                        ">\r\n                                                              </soap:Body>\r\n                                                            </soap:Envelope>"
                     })));
                    }
                    else
                    {
                        writer.Write(this.ConvertToByteArray("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n                                                            <soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n                                                              <soap:Body>\r\n                                                                <" + this._settings.MethodManyFiles + " xmlns=\"" + this._settings.Namespace + "\">\r\n                                                                  <" + this._settings.ParamFiles + ">"));
                        int num = 0;
                        foreach (UploadFilePartInfo info in this._package)
                        {
                            writer.Write(this.ConvertToByteArray("\r\n                                                                <UploadFileInfo>\r\n                                                                  <FileName><![CDATA[" + info.Name + "]]></FileName>\r\n                                                                  <FileData>" + Convert.ToBase64String(info.Data) + "</FileData>\r\n                                                                </UploadFileInfo>"));
                            num++;
                        }
                        writer.Write(this.ConvertToByteArray("\r\n                                                                  </" + this._settings.ParamFiles + ">\r\n                                                                  <" + this._settings.ParamParameters + ">"));
                        foreach (KeyValuePair<string, string> pair2 in this._parameters)
                        {
                            writer.Write(this.ConvertToByteArray("<Parameter>\r\n                                                                    <Key><![CDATA[" + pair2.Key + "]]></Key>\r\n                                                                    <Value><![CDATA[" + pair2.Value + "]]></Value>\r\n                                                                  </Parameter>"));
                        }
                        writer.Write(this.ConvertToByteArray("</" + this._settings.ParamParameters + ">\r\n                                                                </" + this._settings.MethodManyFiles + ">\r\n                                                              </soap:Body>\r\n                                                            </soap:Envelope>"));
                    }
                }
                catch (Exception exception2)
                {
                    this.OnUploadCompleted(false, false, new Exception("There was an error reading the files", exception2), string.Empty);
                    return;
                }
                finally
                {
                    writer.Close();
                }
                try
                {
                    this._request.BeginGetResponse(new AsyncCallback(this.ResponseProceed), null);
                }
                catch (OutOfMemoryException exception3)
                {
                    this.OnUploadCompleted(false, false, new Exception("There was an error trying to send the upload request", exception3), string.Empty);
                }
            }
        }

        private void ResponseProceed(IAsyncResult asyncResult)
        {
            if (!this._hasAborted)
            {
                if (!this._request.HaveResponse)
                {
                    this.OnUploadCompleted(false, true, null, string.Empty);
                }
                else
                {
                    try
                    {
                        HttpWebResponse response = (HttpWebResponse)this._request.EndGetResponse(asyncResult);
                        Exception exc = null;
                        bool success = true;
                        string message = string.Empty;
                        Stream responseStream = response.GetResponseStream();
                        if (responseStream != null)
                        {
                            StreamReader textReader = new StreamReader(response.GetResponseStream());
                            success = true;
                            XDocument document = XDocument.Load(textReader, LoadOptions.SetLineInfo);
                            string localName = (this._package.ServerWaitForOneFile ? this._settings.MethodOneFile : this._settings.MethodManyFiles) + "Result";
                            IEnumerable<XElement> source = from r in document.Descendants(XName.Get(localName, this._settings.Namespace)) select r;
                            message = (source.Count<XElement>() > 0) ? source.First<XElement>().Value : string.Empty;
                            textReader.Close();
                        }
                        if (response.StatusCode == HttpStatusCode.NotFound)
                        {
                            success = false;
                            if (responseStream != null)
                            {
                                exc = new Exception(message);
                            }
                            else
                            {
                                exc = new Exception(string.Format("Internal Server Error in {0}.\n\nThe current Silverlight implementation doesn't allow to provide more detailed errors in POST method.", this._request.RequestUri));
                            }
                        }
                        this.OnUploadCompleted(success, false, exc, message);
                    }
                    catch (Exception)
                    {
                        this.OnUploadCompleted(false, false, new Exception(string.Format("Internal Server Error in {0}.\n\nThe current Silverlight implementation doesn't allow to provide more detailed errors in POST method.", this._request.RequestUri)), string.Empty);
                    }
                }
            }
        }
    }
}
